package machinelearning;

import java.util.Vector;
import weka.core.Instance;
import weka.core.Instances;

/**
 * A Perceptron classifier
 * @author  Anat Nesichi & Moshe Goren
 */
public class Perceptron {

    Vector<Double> targetWeights = new Vector<Double>();
    Vector<Double> learnedWeights;
    int correct = 0, mistakes = 0;

    /**
     * Creates a binary classifier perceptron
     * @param weightsNum size of weight vector
     */
    public Perceptron(int weightsNum)
    {
        targetWeights.ensureCapacity(weightsNum);

        int i = 0;

        // Fill first half with positive weights
        for (; i < weightsNum / 2; i++)
        {
            targetWeights.add(0.25);
        }

        // Fill second half with negative weights
        for (; i < weightsNum; i++)
        {
            targetWeights.add(-0.25);
        }
    }

    /**
     * Resets the <i> learnedWeights, correct, mistake </i> members
     */
    public void ResetPerceptron()
    {
        learnedWeights = new Vector<Double>();
        learnedWeights.ensureCapacity(targetWeights.capacity());

        for (int i = 0; i < targetWeights.capacity(); i++)
            learnedWeights.add(0.0);

        // Reset counters
        correct = 0;
        mistakes = 0;
    }

    /**
     * Creates a random normalized feature vector whose correct classification value
     * is at least <i> gamma </i>
     * @param gamma Limits the classification value of the example
     * @return Example feature vector
     */
    public Vector<Double> CreateExample(double gamma)
    {
        Vector<Double> res;

        do
        {
            res = DoubleVectorUtils.RandomVector(targetWeights.size());
            res = DoubleVectorUtils.NormalizeVector(res);
        }
        while (Math.abs(DoubleVectorUtils.MultiplyVectors(res, targetWeights)) < gamma);

        return res;
    }
    
    /**
     * Tests an example if learned weights give the same sign as the target weights
     * @param example The feature vector to test
     * @param classSign The correct class sign for the given example
     * @return If incorrect: a Double representing the correct sign <br>
     *         If correct: null
     */
    public Double TestStep(Vector<Double> example, double classSign)
    {   
        double wsign = Math.signum(DoubleVectorUtils.MultiplyVectors(example, learnedWeights));        
        //double classSign = Math.signum(DoubleVectorUtils.MultiplyVectors(example, targetWeights));
        

        System.out.print("Learned Weights sign: " + wsign + "; Correct sign: " + classSign + " => ");
        boolean result = wsign == classSign;
        
        if (result)
        {
            correct++;
            System.out.println("Correct " + correct);
            return null;
        }
        else
        {
            mistakes++;
            correct = 0;            
            System.out.println("\nMistake " + mistakes);
            return new Double(classSign);
        }
        
       
    }

    /**
     * Performs a single Perceptron learning step
     * @param example The example to learn from
     * @param classSign  The correct sign for the given example
     *
     *
     * <p>
     * In each step the example is tested using the <i> TestStep </i> method. If the test fails, the learned weights are updated.
     * </p>
     *
     */
    public void LearningStep(Vector<Double> example, double classSign)
    {
        Double correctSign = null;
        if ((correctSign = TestStep(example, classSign)) != null)
        {
            learnedWeights = DoubleVectorUtils.AddVectors(learnedWeights, DoubleVectorUtils.MultiplyVector(example, correctSign));
            System.out.println("Fixed weights to: " + learnedWeights + "\n");
        }
    }

    public Vector<Double> LearnInstances(Instances instances)
    {
        ResetPerceptron();
        int i = 0;
        do
        {
            Instance in = instances.instance(i++);
            Vector<Double> values = DoubleVectorUtils.ConvertFromDoubleArray(in.toDoubleArray(), in.numAttributes() - 1);
            LearningStep(values, in.classValue());

            if (i == instances.numInstances())
                i = 0;
        } while (correct < instances.numInstances());

        return learnedWeights;
    }


    /**
     * Performs the "Perceptron Game"
     * @param gamma Classification score limit for example creation
     * @return The learned weights Vector
     * <p>
     * Continually creates random training set examples (limited by <i> gamma </i>)
     * untill the learned weights are correct over 100 consecutive examples
     */
    public Vector<Double> FindWeightsGame(double gamma)
    {
        ResetPerceptron();

        while (correct < 100)
        {
            Vector<Double> example = CreateExample(gamma);
            double correctSign = Math.signum(DoubleVectorUtils.MultiplyVectors(example, targetWeights));
            LearningStep(example, correctSign);
        }

        System.out.println("Number of mistakes: " + mistakes);
        System.out.println("Final weight vector: " + learnedWeights);
        return learnedWeights;
    }
}
